package engine.operators;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import engine.BoaOperator;
import engine.BoaPopulation;
import engine.ObjectiveFunction;
import engine.Population;
import engine.PopulationEvaluator;
import engine.individuals.ProgramedTrafficNetIndividual;
import engine.individuals.util.IndividualsComparator;

@Deprecated
public class CombineLocalSearchWithEvaluation implements BoaOperator<ProgramedTrafficNetIndividual> {

	private static final Logger LOGGER = Logger.getLogger(
			CombineLocalSearchWithEvaluation.class.getName());
	
	private PopulationEvaluator<ProgramedTrafficNetIndividual> evaluatorOperator;

	private Comparator<ProgramedTrafficNetIndividual> individualsComparator;

	public CombineLocalSearchWithEvaluation(
			PopulationEvaluator<ProgramedTrafficNetIndividual> evaluatorOperator)
	{
		this.evaluatorOperator = evaluatorOperator;
		
		ObjectiveFunction<ProgramedTrafficNetIndividual> objFunction = evaluatorOperator
				.getObjectiveFunctions().get(0);
		this.individualsComparator = new IndividualsComparator<ProgramedTrafficNetIndividual>(
				objFunction);
	}

	/*
	 * (non-Javadoc)
	 * @see engine.Operator#apply(engine.Population)
	 */
	@Override
	public Population<ProgramedTrafficNetIndividual> apply(
			Population<ProgramedTrafficNetIndividual> population) {
		throw new UnsupportedOperationException(
				"Use engine.BoaOperator#apply(engine.BoaPopulation, java.util.List) instead.");
	}

	/*
	 * (non-Javadoc)
	 * @see engine.BoaOperator#apply(engine.BoaPopulation, java.util.List)
	 */
	@Override
	public BoaPopulation<ProgramedTrafficNetIndividual> apply(
			BoaPopulation<ProgramedTrafficNetIndividual> population, 
			List<Population<ProgramedTrafficNetIndividual>> ignore)
	{
		BoaPopulation<ProgramedTrafficNetIndividual> resultPop = population;
		try
		{
			resultPop = applyWithExceptions(population);
		}
		catch (CloneNotSupportedException e)
		{
			LOGGER.severe("Could not apply local search:\n" + e);
		}
		
		return resultPop;
	}

	private BoaPopulation<ProgramedTrafficNetIndividual> applyWithExceptions(
			BoaPopulation<ProgramedTrafficNetIndividual> population)
			throws CloneNotSupportedException 
	{
		BoaPopulation<ProgramedTrafficNetIndividual> popMinus1 = createDecreasedShiftPopulation(population);
		BoaPopulation<ProgramedTrafficNetIndividual> popPlus1 = createIncreasedShiftPopulation(population);
		
		evaluatePopulations(popMinus1, popPlus1);
		
		List<ProgramedTrafficNetIndividual> resultInd = selectBestLocalIndividuals(
				population, popMinus1, popPlus1);
		
		resultInd.addAll(population.getIndividuals()); // join best local and primal pop
		
		BoaPopulation<ProgramedTrafficNetIndividual> resultPop = new BoaPopulation<ProgramedTrafficNetIndividual>(
				resultInd, 
				population.getChromosomeSize());
		
		return resultPop;
	}
	
	private BoaPopulation<ProgramedTrafficNetIndividual> createIncreasedShiftPopulation(
			BoaPopulation<ProgramedTrafficNetIndividual> population)
			throws CloneNotSupportedException
	{
		List<ProgramedTrafficNetIndividual> individuals = population.getIndividuals();
		
		List<ProgramedTrafficNetIndividual> iIndividuals = new ArrayList<ProgramedTrafficNetIndividual>(
				population.getIndividuals().size());
		
		Iterator<ProgramedTrafficNetIndividual> iterator = individuals.iterator();
		
		while (iterator.hasNext())
		{
			ProgramedTrafficNetIndividual ind = iterator
					.next();
			
			ProgramedTrafficNetIndividual indP1 = ind.clone();
			for (int i = 0; i < indP1.getPrograms().length - 1; i++)
			{
//				Integer programShift = indP1.getProgramVariableAt(i);
				
				// FIXME: deprecated because of this!
//				int increasedShift = (programShift + 1)
//						% ProgramedTrafficNetIndividual.MAX_PROGRAM_LENGTH;
				
//				indP1.setShiftAtIndexToValueInSeconds(i, increasedShift);
				
				iIndividuals.add(indP1);
			}
		}
		
		return new BoaPopulation<ProgramedTrafficNetIndividual>(iIndividuals, population.getChromosomeSize());
	}
	
	private BoaPopulation<ProgramedTrafficNetIndividual> createDecreasedShiftPopulation(
			BoaPopulation<ProgramedTrafficNetIndividual> population)
			throws CloneNotSupportedException
	{
		List<ProgramedTrafficNetIndividual> individuals = population.getIndividuals();
		
		List<ProgramedTrafficNetIndividual> dIndividuals = new ArrayList<ProgramedTrafficNetIndividual>(
				population.getIndividuals().size());
		
		Iterator<ProgramedTrafficNetIndividual> iterator = individuals.iterator();
		
		while (iterator.hasNext())
		{
			ProgramedTrafficNetIndividual ind = iterator
			.next();
			
			ProgramedTrafficNetIndividual indM1 = ind.clone();
			for (int i = 0; i < indM1.getPrograms().length - 1; i++)
			{
//				Integer programShift = indM1.getProgramVariableAt(i);
				
//				int decreasedShift = 
//					programShift > 0 
//					? programShift - 1
							// FIXME: deprecated because of this
//							: ProgramedTrafficNetIndividual.MAX_PROGRAM_LENGTH - 1;
				
//				indM1.setShiftAtIndexToValueInSeconds(i, decreasedShift);
				
				dIndividuals.add(indM1);
			}
		}
		
		return new BoaPopulation<ProgramedTrafficNetIndividual>(dIndividuals, population.getChromosomeSize());
	}

	private void evaluatePopulations(
			BoaPopulation<ProgramedTrafficNetIndividual> popMinus1,
			BoaPopulation<ProgramedTrafficNetIndividual> popPlus1)
	{
		LOGGER.info("Applying " + evaluatorOperator.getClass() + " to decreased population");
		applyEvaluation(popMinus1);
		LOGGER.info("Applying " + evaluatorOperator.getClass() + " to increased population");
		applyEvaluation(popPlus1);
	}

	private void applyEvaluation(
			BoaPopulation<ProgramedTrafficNetIndividual> population)
	{
		evaluatorOperator.apply(population);
	}
	
	private List<ProgramedTrafficNetIndividual> selectBestLocalIndividuals(
			BoaPopulation<ProgramedTrafficNetIndividual> population,
			BoaPopulation<ProgramedTrafficNetIndividual> popMinus1,
			BoaPopulation<ProgramedTrafficNetIndividual> popPlus1)
	{
		List<ProgramedTrafficNetIndividual> resultInd = new ArrayList<ProgramedTrafficNetIndividual>(population.size());
		
		Iterator<ProgramedTrafficNetIndividual> popIterator = population.getIndividuals().iterator();
		Iterator<ProgramedTrafficNetIndividual> minus1Iterator = popMinus1.getIndividuals().iterator();
		Iterator<ProgramedTrafficNetIndividual> plus1Iterator = popPlus1.getIndividuals().iterator();
		
		while (popIterator.hasNext()
				&& minus1Iterator.hasNext()
				&& plus1Iterator.hasNext())
		{
			ProgramedTrafficNetIndividual ind = popIterator.next();
			ProgramedTrafficNetIndividual indM1 = minus1Iterator.next();
			ProgramedTrafficNetIndividual indP1 = plus1Iterator.next();
			
			resultInd.add(selectBestIndividual(ind, indM1, indP1));
		}
		return resultInd;
	}
	
	private ProgramedTrafficNetIndividual selectBestIndividual(
			ProgramedTrafficNetIndividual ind, 
			ProgramedTrafficNetIndividual indM1,
			ProgramedTrafficNetIndividual indP1)
	{
		if (individualsComparator.compare(ind, indM1) > 0) // ind > indM1
		{
			if (individualsComparator.compare(ind, indP1) > 0) // indM1 < ind > indP1
			{
				return ind;
			}
			else // indM1 < ind <= indP1
			{
				return indP1;
			}
		}
		else // ind <= indM1
		{
			if (individualsComparator.compare(indM1, indP1) > 0) // ind <= indM1 > indP1
			{
				return indM1;
			}
			else // ind <= indM1 <= indP1
			{
				return indP1;
			}
		}
	}
}
